package com.airport.schedule;

import com.airport.schedule.model.AdHocRule;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.Pair;

import java.util.*;

public class ConflictHelper {

    public final static int HALF_FLIGHT_CONFLICT_MARGIN = 5 * 60;
    private final static int FLIGHT_CONFLICT_MARGIN = 10 * 60;
    private final static int RUNWAY_CONFLICT_MARGIN =  5 * 60;

    public static boolean hasRunwayConflict(Flight f1, FlightGate g1, Flight f2, FlightGate g2) {
        if(f1 == f2) return false; // 同一个航班不存在冲突的说法

        if(g1.getRunwayIn().equals(g2.getRunwayIn())) { // 两个航班是同一个跑道进来的
            int d1 = Math.abs(f1.getArrivalTime()-f2.getArrivalTime()); // 先后到达时间必须有5分钟间隔
            if(d1 < RUNWAY_CONFLICT_MARGIN)
                return true;
        }
        if(g1.getRunwayOut().equals(g2.getRunwayOut())) {// 两个航班同一个跑道出去
            int d2 = Math.abs(f1.getDepartureTime()-f2.getDepartureTime());
            if(d2 < RUNWAY_CONFLICT_MARGIN)
                return true;
        }
        if(g1.getRunwayIn().equals(g2.getRunwayOut())) { // 航班1进来的滑道和航班2出去的滑道一样
            int d3 = Math.abs(f1.getArrivalTime()-RUNWAY_CONFLICT_MARGIN-f2.getDepartureTime());
            if(d3 < RUNWAY_CONFLICT_MARGIN)
                return true;
        }
        if(g1.getRunwayOut().equals(g2.getRunwayIn())) { // 航班1的出去滑道和航班2的进来滑道一样
            int d4 = Math.abs(f1.getDepartureTime()-f2.getArrivalTime()+RUNWAY_CONFLICT_MARGIN);
            if(d4 < RUNWAY_CONFLICT_MARGIN)
                return true;
        }
        return false;
    }

    public static boolean hasFlightGateConflict(Flight f1, Flight f2) {
        if(f1 == f2) return false;
        int a1 = f1.getArrivalTime();
        int d1 = f1.getDepartureTime();
        int a2 = f2.getArrivalTime();
        int d2 = f2.getDepartureTime();
        return !(a1 - d2 >= FLIGHT_CONFLICT_MARGIN || a2 - d1 >= FLIGHT_CONFLICT_MARGIN);
    }

    public static boolean canEnterFlightGate(Flight flight, Flight obstructFlight) {
        int curArrivalTime = flight.getArrivalTime();
        int arrivalTime = obstructFlight.getArrivalTime();
        int departureTime = obstructFlight.getDepartureTime();
        return curArrivalTime <= arrivalTime || curArrivalTime >= departureTime;// 不是同位机不用考虑10分钟限制
    }

    public static boolean canEnterOrExitFlightGate(Flight flight, Flight obstructFlight) {
        int a1 = flight.getArrivalTime();
        int d1 = flight.getDepartureTime();
        int a2 = obstructFlight.getArrivalTime();
        int d2 = obstructFlight.getDepartureTime();
        return a1 >= d2 || a2 >= d1 || (a1 <= a2 && d2 <= d1);
    }

    public static boolean isBothOccured(Flight f1, Flight f2) {
        int a1 = f1.getArrivalTime();
        int a2 = f2.getArrivalTime();
        int d2 = f2.getDepartureTime();
        return (a1 >= a2 && a1 <= d2)
                || Math.abs(a1-a2) < FLIGHT_CONFLICT_MARGIN
                || Math.abs(a1-d2) < FLIGHT_CONFLICT_MARGIN;
    }

    public static Set<Flight> removeConflictFlights(ScheduleSolution solution) {
        System.out.println("开始删除冲突航班..........");
        List<Pair<Flight, Flight>> conflictPairs = findConflictPairs(solution);
        System.out.println("找出冲突对数："+conflictPairs.size());
        Set<Flight> removedFlights = findRemoveFlights(solution, conflictPairs);
        System.out.println("找出删除的航班数："+removedFlights.size());
        removedFlights.forEach(flight -> {
            FlightGate gate = solution.getFlightGateByFlight(flight);
            solution.remove(flight, gate);
        });
        return removedFlights;
    }

    private static Set<Flight> findRemoveFlights(ScheduleSolution solution, List<Pair<Flight, Flight>> conflictPairs) {
        Set<Flight> rmFlights = new HashSet<>();
        Map<Flight, Integer> conflictCounter = new HashMap<>();
        Map<Flight, List<Flight>> map = new HashMap<>();
        for(Pair<Flight, Flight> pair : conflictPairs) {
            conflictCounter.put(pair.head, conflictCounter.getOrDefault(pair.head, 0)+1);
            conflictCounter.put(pair.tail, conflictCounter.getOrDefault(pair.tail, 0)+1);
            List<Flight> hPairs = map.getOrDefault(pair.head, new ArrayList<>());
            hPairs.add(pair.tail);
            map.put(pair.head, hPairs);
            List<Flight> tPairs = map.getOrDefault(pair.tail, new ArrayList<>());
            tPairs.add(pair.head);
            map.put(pair.tail, tPairs);
        }
        while(!conflictCounter.isEmpty()) {
            Map.Entry<Flight, Integer> entry = findLargestConflictEntry(conflictCounter, solution);
            int conflictCount = entry.getValue();
            if(conflictCount > 1) {
                conflictCounter.remove(entry.getKey());
                rmFlights.add(entry.getKey());
                for(Flight conflictSide : map.get(entry.getKey())) {
                    int count = conflictCounter.get(conflictSide);
                    if(count <= 1) {
                        conflictCounter.remove(conflictSide);
                    }else {
                        List<Flight> ls = map.get(conflictSide);
                        for(Flight f : ls) {
                            if(f == entry.getKey()) {
                                ls.remove(f);
                                break;
                            }
                        }
                        conflictCounter.put(conflictSide, count-1);
                    }
                }
            }else if(conflictCount == 1){
                Flight f1 = entry.getKey();
                Flight f2 = map.get(f1).get(0);
                FlightGate g1 = solution.getFlightGateByFlight(f1);
                FlightGate g2 = solution.getFlightGateByFlight(f2);
                solution.remove(f1, g1);
                int c1 = solution.getRunwayConflictNum() + solution.getAdHocFlightNum()
                        - solution.getNearGateFlightNum() - solution.getNearGatePeopleNum();
                solution.assign(f1, g1);
                solution.remove(f2, g2);
                int c2 = solution.getRunwayConflictNum() + solution.getAdHocFlightNum()
                        - solution.getNearGateFlightNum() - solution.getNearGatePeopleNum();
                solution.assign(f2, g2);
                if(c1 > c2) {
                    rmFlights.add(f1);
                }else if(c2 > c1) {
                    rmFlights.add(f2);
                }else {
                    rmFlights.add(f1);
                }
                conflictCounter.remove(f1);
                conflictCounter.remove(f2);
            }
        }
        return rmFlights;
    }

    private static Map.Entry<Flight, Integer> findLargestConflictEntry(Map<Flight, Integer> counter,
                                                                       ScheduleSolution solution) {
        List<Map.Entry<Flight,Integer>> maxEntries = new ArrayList<>();
        int max = -1;
        for(Map.Entry<Flight, Integer> entry : counter.entrySet()) {
            if(entry.getValue() > max) {
                max = entry.getValue();
                maxEntries = new ArrayList<>();
                maxEntries.add(entry);
            }else if(max == entry.getValue()) {
                maxEntries.add(entry);
            }
        }
        if(maxEntries.size() == 1) {
            return maxEntries.get(0);
        }else{
            Map.Entry<Flight,Integer> bestEntry = null;
            int best = Integer.MAX_VALUE;
            for(Map.Entry<Flight,Integer> entry : maxEntries) {
                FlightGate gate = solution.getFlightGateByFlight(entry.getKey());
                solution.remove(entry.getKey(), gate);
                int value = solution.getRunwayConflictNum() + solution.getAdHocFlightNum()
                        - solution.getNearGateFlightNum() - solution.getNearGatePeopleNum();
                solution.assign(entry.getKey(), gate);
                if(value < best) {
                    best = value;
                    bestEntry = entry;
                }
            }
            return bestEntry;
        }
    }

    private static List<Pair<Flight,Flight>> findConflictPairs(ScheduleSolution solution) {
        Set<String> conflictIdPairs = new HashSet<>();

        Problem problem = solution.getProblem();
        List<Pair<Flight, Flight>> pairs = new ArrayList<>();
        solution.getSchedule().forEach((gate, flights)->{
            for(int i = 0; i < flights.size()-1; i++) { // 首先是检查各个机位上的飞机是否有冲突
                Flight iFlight = flights.get(i);
                for(int j = i+1; j < flights.size(); j++) {
                    Flight jFlight = flights.get(j);
                    if(hasFlightGateConflict(iFlight, jFlight)) {
                        pairs.add(new Pair<>(iFlight, jFlight));
                    }
                }
            }
            if(gate.isAdHocGate()) {
                // 如果该机位是临时措施机位 那还得检查受他影响的机位是否有冲突
                AdHocRule rule = gate.getRule();
                List<Flight> obsFlights = getFlights(solution, rule.getObstructFlightGates());
                List<Flight> obsInFlights = getFlights(solution, rule.getObstructRunInFlightGates());
                List<Flight> homoFlights = getFlights(solution, rule.getHomogenyFlightGates());
                for(Flight curFlight : flights) {
                    for(Flight obsFlight : obsFlights) {
                        if(!canEnterOrExitFlightGate(obsFlight, curFlight)) {
                            if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                pairs.add(new Pair<>(curFlight, obsFlight));
                            }
                        }
                    }
                }
                for(Flight curFlight : flights) {
                    for(Flight obsFlight : obsInFlights) {
                        if(!canEnterFlightGate(obsFlight, curFlight)) {
                            if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                pairs.add(new Pair<>(curFlight, obsFlight));
                            }
                        }
                    }
                }
                for(Flight curFlight : flights) {
                    for(Flight obsFlight : homoFlights) {
                        if(hasFlightGateConflict(obsFlight, curFlight)) {
                            if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                pairs.add(new Pair<>(curFlight, obsFlight));
                            }
                        }
                    }
                }

                // 并且他自己是否会被其他临时机位影响
                Set<FlightGate> gates = problem.getAttachedAdhocGates(gate);
                if(gates != null) {
                    for(FlightGate adHocGate : gates) {
                        AdHocRule otherRule = adHocGate.getRule();
                        if(otherRule.getObstructFlightGates().contains(gate.getFlightGateId())) {
                            for(Flight curFlight : flights) {
                                for(Flight obsFlight : solution.getAssignedFlightsByFlightGate(adHocGate)) {
                                    if(!ConflictHelper.canEnterOrExitFlightGate(curFlight, obsFlight)) {
                                        if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                            pairs.add(new Pair<>(curFlight, obsFlight));
                                        }
                                    }
                                }
                            }
                        }
                        if(otherRule.getHomogenyFlightGates().contains(gate.getFlightGateId())) {
                            for(Flight curFlight : flights) {
                                for(Flight obsFlight : solution.getAssignedFlightsByFlightGate(adHocGate)) {
                                    if(hasFlightGateConflict(obsFlight, curFlight)) {
                                        if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                            pairs.add(new Pair<>(curFlight, obsFlight));
                                        }
                                    }
                                }
                            }
                        }
                        if(otherRule.getObstructRunInFlightGates().contains(gate.getFlightGateId())) {
                            for(Flight curFlight : flights) {
                                for(Flight obsFlight : solution.getAssignedFlightsByFlightGate(adHocGate)) {
                                    if(!canEnterFlightGate(curFlight, obsFlight)) {
                                        if(!isAlreadyCompare(conflictIdPairs, curFlight, obsFlight)) {
                                            pairs.add(new Pair<>(curFlight, obsFlight));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        });

        return pairs;
    }

    private static boolean isAlreadyCompare(Set<String> conflictIdPairs, Flight curFlight, Flight affectFlight) {
        String key1 = curFlight.getFlightId()+affectFlight.getFlightId();
        String key2 = affectFlight.getFlightId()+curFlight.getFlightId();
        if(conflictIdPairs.contains(key1) || conflictIdPairs.contains(key2)) return true;
        conflictIdPairs.add(key1);
        conflictIdPairs.add(key2);
        return false;
    }

    private static List<Flight> getFlights(ScheduleSolution sol, Set<String> gateIds) {
        Problem problem = sol.getProblem();
        List<Flight> flights = new ArrayList<>();
        gateIds.forEach(gateId->
            flights.addAll(sol.getAssignedFlightsByFlightGate(problem.getFlightGateById(gateId))));
        return flights;
    }
}
